package com.sccba.uop.demo.util.algorithm;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

public class SM2Utils {

    private static final String SM2_EC = "sm2p256v1";

    private static final X9ECParameters SM2_PARAMS = GMNamedCurves.getByName(SM2_EC);
    private static final ECDomainParameters DOMAIN_PARAMETERS = new ECDomainParameters(SM2_PARAMS.getCurve(), SM2_PARAMS.getG(), SM2_PARAMS.getN());

    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    public static byte[] readPublicKeyFromCer(String certPath) {
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
            X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(Files.newInputStream(Paths.get(certPath)));
            byte[] pubKey = new byte[64];
            System.arraycopy(SubjectPublicKeyInfo.getInstance(certificate.getPublicKey().getEncoded()).getPublicKeyData().getEncoded(), 4, pubKey, 0, pubKey.length);
            return ByteUtils.fromHexString("04" + ByteUtils.toHexString(pubKey));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (CertificateException | NoSuchProviderException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 加密
     *
     * @param publicKey 公钥
     * @param plainData 明文字节
     * @return
     */
    public static byte[] encrypt(byte[] publicKey, byte[] plainData) {
        ECPoint publicKeyPoint = SM2_PARAMS.getCurve().decodePoint(publicKey);
        ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(publicKeyPoint, DOMAIN_PARAMETERS);

        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        sm2Engine.init(true, new ParametersWithRandom(publicKeyParams, new SecureRandom()));

        try {
            byte[] cipherData = sm2Engine.processBlock(plainData, 0, plainData.length);
            return cipherData;

        } catch (InvalidCipherTextException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 解密
     *
     * @param privateKey 私钥
     * @param cipherData 密文字节
     * @return
     */
    public static byte[] decrypt(byte[] privateKey, byte[] cipherData) {

        BigInteger privateKeyInt = new BigInteger(privateKey);
        ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(privateKeyInt, DOMAIN_PARAMETERS);

        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        sm2Engine.init(false, privateKeyParams);

        try {
            byte[] plainData = sm2Engine.processBlock(cipherData, 0, cipherData.length);
            return plainData;
        } catch (InvalidCipherTextException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 基于SM3签名
     *
     * @param prvKey 私钥
     * @param bytes  签名数据字节
     * @return
     */
    public static byte[] signWithSm3(byte[] prvKey, byte[] bytes) {
        try {

            ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(new BigInteger(prvKey), DOMAIN_PARAMETERS);

            ParametersWithID parametersWithID = new ParametersWithID(new ParametersWithRandom(privateKeyParams, new SecureRandom()), "1234567812345678".getBytes(StandardCharsets.UTF_8));

            SM2Signer sm2Signer = new SM2Signer();
            sm2Signer.init(true, parametersWithID);
            sm2Signer.update(bytes, 0, bytes.length);
            byte[] signature = sm2Signer.generateSignature();

            return signature;
        } catch (CryptoException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 基于SM3校验签名
     *
     * @param pubKey    公钥
     * @param bytes     签名数据字节
     * @param signature 签名字节
     * @return
     */
    public static boolean verifyWithSm3(byte[] pubKey, byte[] bytes, byte[] signature) {

        ECPoint publicKeyPoint = SM2_PARAMS.getCurve().decodePoint(pubKey);
        ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(publicKeyPoint, DOMAIN_PARAMETERS);

        ParametersWithID parametersWithID = new ParametersWithID(publicKeyParams, "1234567812345678".getBytes(StandardCharsets.UTF_8));
        SM2Signer sm2Signer = new SM2Signer();
        sm2Signer.init(false, parametersWithID);
        sm2Signer.update(bytes, 0, bytes.length);

        boolean verify = sm2Signer.verifySignature(signature);
        return verify;
    }

}
